﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TableBuilder.cs" company="Lynx Toolkit">
//   The MIT License (MIT)
//   
//   Copyright (c) 2012 Oystein Bjorke
//   
//   Permission is hereby granted, free of charge, to any person obtaining a
//   copy of this software and associated documentation files (the
//   "Software"), to deal in the Software without restriction, including
//   without limitation the rights to use, copy, modify, merge, publish,
//   distribute, sublicense, and/or sell copies of the Software, and to
//   permit persons to whom the Software is furnished to do so, subject to
//   the following conditions:
//   
//   The above copyright notice and this permission notice shall be included
//   in all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
//   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
// <summary>
//   Provides a table builder.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace LynxToolkit.Documents.Spreadsheet
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// Provides a table builder.
    /// </summary>
    /// <typeparam name="T">
    /// The type of the table items.
    /// </typeparam>
    public class TableBuilder<T>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TableBuilder{T}"/> class.
        /// </summary>
        public TableBuilder()
        {
            this.Definitions = new List<BuilderDefinition>();
        }

        /// <summary>
        /// Gets the definitions.
        /// </summary>
        public List<BuilderDefinition> Definitions { get; private set; }

        /// <summary>
        /// Adds a column or row definition to the table.
        /// </summary>
        /// <param name="header">
        /// The header.
        /// </param>
        /// <param name="cellValueFunction">
        /// The cell value function.
        /// </param>
        /// <param name="cellStyle">
        /// The cell style.
        /// </param>
        /// <param name="headerStyle">
        /// The header style.
        /// </param>
        public void Add(string header, Func<T, object> cellValueFunction, Style cellStyle = null, Style headerStyle = null)
        {
            this.Definitions.Add(
                new BuilderDefinition
                    {
                        Header = header,
                        CellValueFunction = cellValueFunction,
                        Style = cellStyle,
                        HeaderStyle = headerStyle
                    });
        }

        /// <summary>
        /// Auto generates the definitions.
        /// </summary>
        /// <param name="headerStyle">The header style.</param>
        /// <param name="ignoreAttributes">The ignore attribute types.</param>
        public void AutoGenerateDefinitions(Style headerStyle = null, params Type[] ignoreAttributes)
        {
            var type = typeof(T);
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (ignoreAttributes != null)
                {
                    if (ignoreAttributes.Any(ia => property.GetCustomAttributes(ia, true).Length > 0))
                    {
                        // One of the ignore attributes were found in the property's attributes.
                        continue;
                    }
                }

                var p = property;
                this.Definitions.Add(
                    new BuilderDefinition
                        {
                            Header = property.Name,
                            HeaderStyle = headerStyle,
                            CellValueFunction = item => p.GetValue(item, null)
                        });
            }
        }

        /// <summary>
        /// Places the specified items on the specified spreadsheet.
        /// </summary>
        /// <param name="items">
        /// The items.
        /// </param>
        /// <param name="sheet">
        /// The sheet.
        /// </param>
        /// <param name="startAt">
        /// The top left position of the table.
        /// </param>
        /// <param name="itemsInRows">
        /// Determines whether the items should be oriented in rows (<c>true</c>) or columns (<c>false</c>).
        /// </param>
        public void Place(List<T> items, Worksheet sheet, string startAt, bool itemsInRows = true)
        {
            if (this.Definitions.Count == 0)
            {
                this.AutoGenerateDefinitions();
            }

            var startRef = CellReference.Parse(startAt);
            for (int j = 0; j < this.Definitions.Count; j++)
            {
                int row = itemsInRows ? startRef.Row : startRef.Row + j;
                int column = itemsInRows ? startRef.Column + j : startRef.Column;
                sheet[row, column] = this.Definitions[j].Header;
                sheet.ApplyStyle(row, column, this.Definitions[j].HeaderStyle);
            }

            for (int i = 0; i < items.Count; i++)
            {
                for (int j = 0; j < this.Definitions.Count; j++)
                {
                    int row = itemsInRows ? startRef.Row + i + 1 : startRef.Row + j;
                    int column = itemsInRows ? startRef.Column + j : startRef.Column + i + 1;
                    sheet[row, column] = this.Definitions[j].CellValueFunction(items[i]);
                    sheet.ApplyStyle(row, column, this.Definitions[j].Style);
                }
            }
        }

        /// <summary>
        /// Represents a row/column definition.
        /// </summary>
        public class BuilderDefinition
        {
            /// <summary>
            /// Gets or sets the cell value function.
            /// </summary>
            /// <value>The cell value function.</value>
            public Func<T, object> CellValueFunction { get; set; }

            /// <summary>
            /// Gets or sets the header.
            /// </summary>
            /// <value>The header.</value>
            public string Header { get; set; }

            /// <summary>
            /// Gets or sets the header style.
            /// </summary>
            /// <value>The header style.</value>
            public Style HeaderStyle { get; set; }

            /// <summary>
            /// Gets or sets the style.
            /// </summary>
            /// <value>The style.</value>
            public Style Style { get; set; }
        }
    }
}